import base64
import datetime
import re
import time
from collections.abc import Callable
from typing import TYPE_CHECKING, Optional

import frappe
from frappe import _
from frappe.model import log_types
from frappe.monitor import get_trace_id
from frappe.query_builder import DocType
from frappe.utils import cint, cstr, now_datetime

if TYPE_CHECKING:
	from frappe.model.document import Document
	from frappe.model.meta import Meta

def get_custom_series(doc_type,name_class, digits):
	# series created ?
	# Using frappe.qb as frappe.get_values does not allow order_by=None
	series = DocType("teo_series")
	current = (frappe.qb.from_(series).where((series.doc_type == doc_type) & (series.name_class == name_class)).for_update().select("current")).run()
 
	if current and current[0][0] is not None:
		current = current[0][0]
		# yes, update it
		frappe.db.sql("UPDATE `tabteo_series` SET `current` = `current` + 1 WHERE `doc_type`=%s and `name_class` = %s", (doc_type,name_class,))
		current = cint(current) + 1
	else:
		# no, create it
		hash_key = (_get_timestamp_prefix() + _generate_random_string(7))[:10]
		frappe.db.sql("INSERT INTO `tabteo_series` (`name`,`doc_type`,`name_class`, `current`) VALUES (%s,%s,%s, 1)", (hash_key,doc_type,name_class,))
		current = 1
	return ("%0" + str(digits) + "d") % current

def _get_timestamp_prefix():
	ts = int(time.time() * 10)  # time in deciseconds
	# we ~~don't need~~ can't get ordering over entire lifetime, so we wrap the time.
	ts = ts % (32**4)
	ts_part = base64.b32hexencode(ts.to_bytes(length=5, byteorder="big")).decode()[-3:].lower()

	# First character is from request/job specific UUID, all documents created in this "session" will
	# have same prefix. This avoids collision between parallel jobs with reasonable probabililistic
	# guarantees.
	request_part = (get_trace_id() or "")[-1:]

	return request_part + ts_part


def _generate_random_string(length=10):
	"""Better version of frappe.generate_hash for naming.

	This uses entire base32 instead of base16 used by generate_hash. So it has twice as many
	characters and hence more likely to have shorter common prefixes. i.e. slighly faster comparisons and less conflicts.

	Why not base36?
	It's not in standard library else using all characters is probably better approach.
	Why not base64?
	MySQL is case-insensitive, we can't use both upper and lower case characters.
	"""
	from secrets import token_bytes as get_random_bytes

	return base64.b32hexencode(get_random_bytes(length)).decode()[:length].lower()
